package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	bolt "github.com/coreos/bbolt"
	d "github.com/studio-b12/gowebdav"
	"gopkg.in/ini.v1"
)

type UserInfo struct {
	ID       uint64
	UsrName  string
	Password string
	Url      string
}

type FileNode struct {
	LocalPath  string
	RemotePath string
	Dir        bool
	Pulltime   int64
	Localtime  int64 `json:"localtime,omitempty"`
}

const (
	CmdTypelsUsr = iota
	CmdTypedelUsr
	CmdTypelsDir
	CmdTypepullFile
	CmdTypePushFile
	CmdTypeFile
	CmdTypeStatus
	CmdTypeAdd
	CmdTypeDel
)

var _usrdb *bolt.DB

const (
	TextBlack = iota + 30
	TextRed
	TextGreen
	TextYellow
	TextBlue
	TextMagenta
	TextCyan
	TextWhite
)
const cfgfilename = "cfg.ini"
const cfgsectionname = "conf"

func Red(msg string) string {
	return SetColor(msg, 0, 0, TextRed)
}

func SetColor(msg string, conf, bg, text int) string {
	return fmt.Sprintf("%c[%d;%d;%dm%s%c[0m", 0x1B, conf, bg, text, msg, 0x1B)
}

func main() {

	lsusr := flag.Bool("lsusr", false, "-lsusr list all register users")
	_lsdir := flag.String("ls", "", "-ls r/l,dir  list remote or local directory content")
	_pullfile := flag.String("pull", "", "-pull remotedir/remotefile[,remotedir/remotefile,...],localdir/localfile   pull remote file or directory to local")
	_filestat := flag.String("file", "", "-file dir/file   show file or diectory status")
	_pushfile := flag.String("push", "", "-push localdir/localfile[,localdir/localfile,...], push local file or directory to remote")
	_status := flag.String("status", "", "-status dir/file  show file or directory status")
	_add := flag.String("add", "", "-add dir/file[,localdir/localfile]")
	_del := flag.String("del", "", "-del dir/file[,localfile/localdir]")
	defaultusr := flag.String("setcurrent", "", "-setcurrent username")

	flag.Parse()

	lsdir := strings.Split(*_lsdir, ",")
	pullfile := strings.Split(*_pullfile, ",")
	filestat := strings.Split(*_filestat, ",")
	pushfile := strings.Split(*_pushfile, ",")
	status := strings.Split(*_status, ",")
	add := strings.Split(*_add, ",")
	del := strings.Split(*_del, ",")

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	dbpath := strings.Replace(dir, "\\", "/", -1)
	dbpath += "/userdata.db"
	_usrdb, err = bolt.Open(dbpath, 0660, nil)

	if err != nil {
		fail("Failed Open DB")
	}

	defer _usrdb.Close()

	usrinfo := []UserInfo{}
	_usrdb.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("usrinfo"))
		usrchan := make(chan UserInfo)
		if b == nil {
			var gonext string
			fmt.Println("First Run, Input email and password?(y/n)")
			fmt.Scanln(&gonext)
			if strings.EqualFold(gonext, "y") == false {
				return nil
			}
			go createUser(_usrdb, usrchan)
			usrinfo = append(usrinfo, <-usrchan)
			return nil
		}
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			//fmt.Printf("key=%s, value=%s\n", k, v)
			var uinfo UserInfo
			err := json.Unmarshal(v, &uinfo)
			if err != nil {
				return err
			}
			usrinfo = append(usrinfo, uinfo)
		}

		return nil
	})

	if len(*defaultusr) != 0 {
		findusr := false
		for _, v := range usrinfo {
			if v.UsrName == *defaultusr {
				conf, err := ini.Load(cfgfilename)
				if err != nil {
					os.Create(cfgfilename)
					conf, _ = ini.Load(cfgfilename)
					conf.NewSection(cfgsectionname)
				}
				sec := conf.Section(cfgsectionname)
				sec.Key("default").SetValue(strconv.FormatUint(v.ID, 10))
				conf.SaveTo(cfgfilename)
				findusr = true
				break
			}
		}
		if !findusr {
			fmt.Println("can't find default user")
		}
	}

	if *lsusr {
		for _, v := range usrinfo {
			fmt.Printf("%d %s url:%s\n", v.ID, v.UsrName, v.Url)
		}
		return
	}

	if len(pullfile) >= 2 {
		dispatchCmd(CmdTypepullFile, usrinfo, pullfile)
	}
	if len(lsdir) == 2 {
		dispatchCmd(CmdTypelsDir, usrinfo, lsdir)
	}

	if len(filestat) == 2 {
		dispatchCmd(CmdTypeFile, usrinfo, filestat)
	}

	if len(add) >= 1 && len(add[0]) != 0 {
		dispatchCmd(CmdTypeAdd, usrinfo, add)
	}

	if len(pushfile) >= 1 && len(pushfile[0]) != 0 {
		dispatchCmd(CmdTypePushFile, usrinfo, pushfile)
	}

	if len(status) == 1 && len(status[0]) != 0 {
		dispatchCmd(CmdTypeStatus, usrinfo, status)
	}

	if len(del) >= 1 && len(del[0]) != 0 {
		dispatchCmd(CmdTypeDel, usrinfo, del)
	}
}

func dispatchCmd(cmd int, usrinfo []UserInfo, args []string) error {
	argslen := len(args)
	var err error
	var uinfo *UserInfo
	var client *d.Client

	conf, err := ini.Load(cfgfilename)
	if err != nil {
		return err
	}
	sec := conf.Section(cfgsectionname)
	uID, err := sec.Key("default").Uint64()
	if err != nil {
		return err
	}

	if argslen != 0 {
		if err == nil {
			for _, v := range usrinfo {
				if uID == v.ID {
					uinfo = &v
					break
				}
			}
		} else {
			return err
		}
	}

	if uinfo != nil {
		client = d.NewClient(uinfo.Url, uinfo.UsrName, uinfo.Password)
	}

	switch cmd {
	case CmdTypeFile:

		file, err := client.Stat(args[0])
		if err == nil {
			fmt.Println(file)
		}

	case CmdTypelsDir:
		switch args[0] {
		case "r":
			files, err := client.ReadDir(args[1])
			if err == nil {
				fmt.Println(fmt.Sprintf("ReadDir: '%s' entries: %d ", args[1], len(files)))
				for _, f := range files {
					fmt.Println(f)
				}
			}
		case "l":
			locadir, err := filepath.Abs(args[1])
			if err != nil {
				return err
			}
			_usrdb.View(func(tx *bolt.Tx) error {
				bkt := tx.Bucket([]byte("fileinfo"))
				if bkt == nil {
					return nil
				}
				cur := bkt.Cursor()
				for k, v := cur.First(); k != nil; k, v = cur.Next() {
					finfo := FileNode{}
					err := json.Unmarshal(v, &finfo)
					if err != nil {
						return err
					}

					finfodir, _ := filepath.Split(finfo.LocalPath)
					if len(finfodir) >= len(locadir) {
						if newfinfodir := []byte(finfodir)[0:len(locadir)]; locadir == string(newfinfodir) {
							fmt.Println(finfo)
						}
					}
				}
				return nil
			})
		}

	case CmdTypepullFile:

		err = pullRemote(client, args[0:len(args)-1], args[len(args)-1])

	case CmdTypePushFile:

		err = pushRemote(client, args)

	case CmdTypeStatus:

		fileStatus(client, args[0])

	case CmdTypeAdd:

		fileadded(client, args)

	case CmdTypeDel:

		filedel(client, args)

	}
	return err
}

func createUser(db *bolt.DB, usr_info chan UserInfo) error {
	tx, err := db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	// Setup the users bucket.
	bkt, err := tx.CreateBucketIfNotExists([]byte("usrinfo"))
	if err != nil {
		return err
	}

	var u UserInfo
	var Username, UserPass, WebDavUrl string
	// Generate an ID for the new user.
	userID, err := bkt.NextSequence()
	if err != nil {
		return err
	}
	u.ID = userID

	fmt.Println("Enter User:")
	fmt.Scanln(&Username)
	fmt.Println("Enter Password:")
	fmt.Scanln(&UserPass)
	fmt.Println("Enter WebDav Url:")
	fmt.Scanln(&WebDavUrl)

	u.UsrName = Username
	u.Password = UserPass
	u.Url = WebDavUrl
	// Marshal and save the encoded user.
	if buf, err := json.Marshal(u); err != nil {
		return err
	} else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil {
		return err
	}

	// Commit the transaction.
	if err := tx.Commit(); err != nil {
		return err
	}

	usr_info <- u
	return nil
}

func getRemoteDirFileNode(c *d.Client, dir string, localdir string) []FileNode {
	//rdir, err := c.Stat(dir)
	//if err != nil {
	//	return nil
	//}
	files, err := c.ReadDir(dir)
	if err == nil {
		fnodes := []FileNode{}
		ro := filepath.Join(dir, "/") + "/"
		filed := FileNode{Dir: true, RemotePath: ro,
			LocalPath: filepath.Join(localdir, "/") + "/",
			Localtime: 0,
			Pulltime:  0} //rdir.ModTime().Unix()}
		fnodes = append(fnodes, filed)

		for _, f := range files {
			nodelocalpath := filepath.Join(localdir, f.Name())
			noderemotepath := filepath.ToSlash(filepath.Join(dir, f.Name()))
			if f.IsDir() {
				lsdir := getRemoteDirFileNode(c, noderemotepath, nodelocalpath)
				if lsdir != nil {
					fnodes = append(fnodes, lsdir...)
				}
			} else {
				node := FileNode{Dir: f.IsDir(), Pulltime: f.ModTime().Unix(), Localtime: f.ModTime().Unix(),
					LocalPath: nodelocalpath, RemotePath: noderemotepath}

				fnodes = append(fnodes, node)
			}
		}
		return fnodes
	}
	return nil
}

func findInDB(node string) *FileNode {
	flag := false
	finfo := FileNode{}
	_usrdb.View(func(tx *bolt.Tx) error {
		bkt := tx.Bucket([]byte("fileinfo"))
		if bkt == nil {
			return nil
		}
		if v := bkt.Get([]byte(node)); v != nil {
			flag = true
			err := json.Unmarshal(v, &finfo)
			if err != nil {
				return err
			}
			if floc, ferr := os.Stat(finfo.LocalPath); ferr == nil {
				finfo.Localtime = floc.ModTime().Unix()
			} else {
				finfo.Localtime = 0
			}
		}
		return nil
	})
	if flag {
		return &finfo
	}
	return nil
}

func fileadded(c *d.Client, nodes []string) {
	_usrdb.Update(func(tx *bolt.Tx) error {
		bkt := tx.Bucket([]byte("fileinfo"))
		if bkt == nil {
			return nil
		}
		for _, node := range nodes {
			if _, err := os.Stat(node); err != nil {
				fmt.Println(fmt.Sprintf("%s stat err: %s", node, err.Error()))
				continue
			}
			if findInDB(node) == nil {
				abspath, err := filepath.Abs(node)
				if err != nil {
					fmt.Println(fmt.Sprintf("%s file error: %s", node, err.Error()))
					return err
				}
				pathslice := strings.Split(abspath, "/")
				lastfnode := &FileNode{}
				index := len(pathslice) - 1
				for ; index >= 1; index-- {
					newpath := "/"
					for n := 1; n < index; n++ {
						newpath += pathslice[n] + "/"
					}
					lastfnode = findInDB(newpath)
					if lastfnode != nil {
						break
					}
				}
				if lastfnode == nil {
					fmt.Println("create root directory is unsupported!")
					return nil
				}

				newpath := "/"
				for n := 1; n < index; n++ {
					newpath += pathslice[n] + "/"
				}
				addfilenode := lastfnode
				for ; index < len(pathslice); index++ {
					newpath += pathslice[index]
					if ff, err := os.Stat(newpath); err == nil {
						if ff.IsDir() {
							newpath += "/"
							addfilenode.Localtime = ff.ModTime().Unix()
							addfilenode.Dir = true
						} else {
							addfilenode.Dir = false
						}
					} else {
						return err
					}
					addfilenode.LocalPath = newpath
					addfilenode.Pulltime = 0
					addfilenode.RemotePath = filepath.Join(addfilenode.RemotePath, pathslice[index])

					if buf, err := json.Marshal(addfilenode); err != nil {
						return err
					} else if err := bkt.Put([]byte(addfilenode.LocalPath), buf); err != nil {
						return err
					}
				}
				fmt.Println(fmt.Sprintf("%s file add success", node))
			} else {
				fmt.Println(fmt.Sprintf("%s file is in db", node))
			}
		}
		return nil
	})

}

func filedel(c *d.Client, nodes []string) {

	_usrdb.Update(func(tx *bolt.Tx) error {
		bkt := tx.Bucket([]byte("fileinfo"))
		if bkt == nil {
			return nil
		}
		for _, node := range nodes {
			node, err := filepath.Abs(node)
			if err != nil {
				continue
			}
			if fnode, err := os.Stat(node); err == nil {
				newnodename := node
				nodeisDir := false
				if fnode.IsDir() {
					newnodename += "/"
					nodeisDir = true
				}
				if bkt.Get([]byte(newnodename)) == nil {
					fmt.Println(fmt.Sprintf("%s file is not in db", node))
					continue
				}
				nodedir, nodename := filepath.Split(node)
				delname := fileName2DelName(nodename)
				if nodeisDir == false {
					f1, _ := os.Create(filepath.Join(nodedir, delname))
					defer f1.Close()
				} else {
					os.Mkdir(filepath.Join(nodedir, delname), 0666)
				}
				os.RemoveAll(node)
			}
		}
		return nil
	})
}

func fileStatus(c *d.Client, node string) {
	nodestatus := func(node1 string) {
		finfo := findInDB(node1)
		if finfo != nil {
			if finfo.Localtime == 0 {
				if isFileinDel(finfo.LocalPath) {
					fmt.Println(Red(fmt.Sprintf("File %s in Delete", finfo.LocalPath)))
				} else {
					fmt.Println(Red(fmt.Sprintf("File %s is Missing", finfo.LocalPath)))
				}
			} else {
				rinfo, err := c.Stat(finfo.RemotePath)
				if err == nil {
					if finfo.Pulltime != finfo.Localtime {
						if rinfo.ModTime().Unix() == finfo.Pulltime {
							fmt.Println(Red(fmt.Sprintf("File %s is modified local", finfo.LocalPath)))
						} else {
							fmt.Println(Red(fmt.Sprintf("File %s is conflict", finfo.LocalPath)))
						}
					} else {
						if rinfo.ModTime().Unix() != finfo.Pulltime {
							fmt.Println(Red(fmt.Sprintf("File %s is modified remote", finfo.LocalPath)))
						} else {

						}
					}
				} else {
					fmt.Println("remote file " + finfo.RemotePath + " error: " + err.Error())
				}
			}
		} else {
			fmt.Println("find " + node + " no record in DB")
		}
	}
	pathdir, _ := filepath.Split(node)
	if fnode, err := os.Stat(node); err == nil {
		if fnode.IsDir() {
			dirs, _ := ioutil.ReadDir(node)
			for _, node1 := range dirs {
				nodestatus(filepath.Join(pathdir, node1.Name()))
			}
		} else {
			nodestatus(node)
		}
	}
}

func fileName2DelName(fName string) string {
	return string("." + fName + "_del")
}

func isFileinDel(fpath string) bool {
	filedel := false
	if _, err := os.Stat(fpath); err != nil || os.IsNotExist(err) {
		fdir, filename := filepath.Split(filepath.Join(fpath, ""))
		newFilePath := filepath.Join(fdir, fileName2DelName(filename))
		if _, err = os.Stat(newFilePath); err == nil || os.IsExist(err) {
			filedel = true
		}
	}
	return filedel
}

type nodeHandler struct {
	reason int
	node   FileNode
}

const (
	addNode = iota
	deleteNode
	conflictNode
)

func pushRemote(c *d.Client, local []string) error {

	dblocaladd := []FileNode{}
	dblocaldel := []FileNode{}
	_usrdb.Update(func(tx *bolt.Tx) error {
		bkt := tx.Bucket([]byte("fileinfo"))
		if bkt == nil {
			return nil
		}

		addFileFunc := func(lo FileNode) error {
			remotefile, err := c.Stat(lo.RemotePath)
			if lo.Dir {
				if err != nil || !remotefile.IsDir() {
					c.MkdirAll(lo.RemotePath, 0755)
					fmt.Println(fmt.Sprintf("make remote dir : %s", lo.RemotePath))
				}
			} else {
				needPush := false
				lf, lerr := os.Stat(lo.LocalPath)
				if err != nil || (lerr == nil && (remotefile.IsDir() || (remotefile.ModTime().Unix() < lf.ModTime().Unix()))) {
					needPush = true
				}
				if needPush && err == nil && !remotefile.IsDir() && remotefile.ModTime().Unix() != lo.Pulltime {
					needPush = false
					fmt.Println(fmt.Sprintf("file %s is conflict", lo.LocalPath))
				}
				if needPush {
					stream, err := getStream(lo.LocalPath)
					if err != nil {
						fmt.Println(fmt.Sprintf("getStream:%s,error:%v", lo.LocalPath, err))
					}

					if err = c.WriteStream(lo.RemotePath, stream, 0644); err == nil {
						fmt.Println("Put: " + lo.LocalPath + " -> " + lo.RemotePath)
					}
					stream.Close()
				}
			}
			return err
		}

		delFileFunc := func(lo FileNode) error {
			remotefile, err := c.Stat(lo.RemotePath)
			if err == nil && !remotefile.IsDir() && remotefile.ModTime().Unix() != lo.Pulltime {
				fmt.Println(fmt.Sprintf("file %s is conflict", lo.LocalPath))
				return nil
			}
			c.RemoveAll(lo.RemotePath)
			dir, name := filepath.Split(filepath.Join(lo.LocalPath, ""))
			os.RemoveAll(filepath.Join(dir, fileName2DelName(name)))
			bkt.Delete([]byte(lo.LocalPath))
			fmt.Println(fmt.Sprintf("Remove file: %s", lo.RemotePath))
			return nil
		}

		cur := bkt.Cursor()
		for _, lo := range local {
			lo, err := filepath.Abs(lo)
			if err != nil {
				continue
			}
			for k, v := cur.First(); k != nil; k, v = cur.Next() {
				finfo := FileNode{}
				err := json.Unmarshal(v, &finfo)
				if err != nil {
					return err
				}

				if floc, ferr := os.Stat(lo); ferr == nil {

					if floc.IsDir() {
						flocdir, _ := filepath.Split(lo)
						finfodir, _ := filepath.Split(finfo.LocalPath)
						if len(finfodir) >= len(flocdir) {
							if newfinfodir := []byte(finfodir)[0:len(flocdir)]; flocdir == string(newfinfodir) {
								if isFileinDel(finfo.LocalPath) {
									dblocaldel = append(dblocaldel, finfo)
								} else {
									dblocaladd = append(dblocaladd, finfo)
								}
							}
						}

					} else {
						if lo == finfo.LocalPath {
							if isFileinDel(finfo.LocalPath) {
								dblocaldel = append(dblocaldel, finfo)
							} else {
								dblocaladd = append(dblocaladd, finfo)
							}
							break
						}
					}
				}
			}

		}

		for _, lo := range dblocaladd {
			addFileFunc(lo)
		}

		for _index, _ := range dblocaldel {
			_reverseindex := len(dblocaldel) - 1 - _index
			lo := dblocaldel[_reverseindex]
			delFileFunc(lo)
		}
		return nil

	})

	return nil
}

func pullRemote(c *d.Client, remote []string, local string) (err error) {

	dbremote := []FileNode{}
	dblocal := []FileNode{}

	//	_usrdb.Update(func(tx *bolt.Tx) error {
	//		tx.DeleteBucket([]byte("fileinfo"))
	//		return nil
	//	})

	local, err = filepath.Abs(local)
	if err != nil {
		return err
	}
	lol, err := os.Stat(local)
	islocalfile := false
	if err != nil || lol.IsDir() == false {
		islocalfile = true
	}
	if len(remote) > 1 && islocalfile {
		return errors.New("local is not directory")
	}

	_usrdb.View(func(tx *bolt.Tx) error {
		bkt := tx.Bucket([]byte("fileinfo"))
		if bkt == nil {
			return nil
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			finfo := FileNode{}
			err := json.Unmarshal(v, &finfo)
			if err != nil {
				return err
			}
			if floc, ferr := os.Stat(finfo.LocalPath); ferr == nil && !floc.IsDir() {
				finfo.Localtime = floc.ModTime().Unix()
			} else {
				finfo.Localtime = 0
			}
			dblocal = append(dblocal, finfo)
		}
		return nil
	})

	for _, ro := range remote {
		ro, err := filepath.Abs(ro)
		if err != nil {
			continue
		}
		file, err := c.Stat(ro)
		if err != nil {
			return err
		}

		if file.IsDir() {
			if islocalfile {
				return errors.New("local is not directory")
			}
			//filed := FileNode{Dir: file.IsDir(), RemotePath: ro,
			//	LocalPath: filepath.Join(local, file.Name()) + "/",
			//	Localtime: 0,
			//	Pulltime:  0}
			//dbremote = append(dbremote, filed)
			lsdir := getRemoteDirFileNode(c, ro, filepath.Join(local, file.Name()))
			if lsdir != nil {
				dbremote = append(dbremote, lsdir...)
			}
		} else {
			filed := FileNode{Dir: file.IsDir(), RemotePath: ro,
				LocalPath: filepath.Join(local, file.Name()),
				Localtime: file.ModTime().Unix(),
				Pulltime:  file.ModTime().Unix()}
			dbremote = append(dbremote, filed)
		}
	}

	fileNodeListHandler := []nodeHandler{}

	//merge local & remote file
	if len(dblocal) == 0 {
		for _, n := range dbremote {
			fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: addNode, node: n})
		}
	} else {
		bitmap := make([]bool, len(dblocal))

		for _, re := range dbremote {
			find := false
			for _index := 0; _index < len(dblocal); _index++ {
				lo := dblocal[_index]

				if re.LocalPath == lo.LocalPath {
					if re.Pulltime > lo.Pulltime {
						if lo.Pulltime == lo.Localtime || lo.Localtime == 0 {
							fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: addNode, node: re})
						} else {
							fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: conflictNode, node: re})
						}
					}
					bitmap[_index] = true
					find = true
					break
				}
			}
			if !find {
				fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: addNode, node: re})
			}
		}

		for _index := 0; _index < len(bitmap); _index++ {
			if !bitmap[_index] {
				_needdel := true
				_, _err := os.Stat(dblocal[_index].LocalPath)
				if _err == nil {
					if dblocal[_index].Localtime != dblocal[_index].Pulltime {
						_needdel = false
						fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: conflictNode, node: dblocal[_index]})
					}
				}
				if _needdel {
					fileNodeListHandler = append(fileNodeListHandler, nodeHandler{reason: deleteNode, node: dblocal[_index]})
				}
			}
		}
	}

	delayListDel := []FileNode{}
	err = _usrdb.Update(func(tx *bolt.Tx) error {
		bkt, err := tx.CreateBucketIfNotExists([]byte("fileinfo"))
		if err != nil {
			return err
		}

		conflictList := []string{}
		for _, fhandler := range fileNodeListHandler {
			fnode := fhandler.node
			switch fhandler.reason {
			case addNode:
				if fnode.Dir {
					os.MkdirAll(fnode.LocalPath, os.ModePerm)
					fmt.Println(fmt.Sprintf("Create %s Directory", fnode.LocalPath))
				} else {
					bytes, err := c.Read(fnode.RemotePath)
					if err != nil {
						return err
					}

					if err = writeFile(fnode.LocalPath, bytes, 0644); err == nil {
						fmt.Println(fmt.Sprintf("Written %d bytes to: %s", len(bytes), fnode.LocalPath))
					}
					os.Chtimes(fnode.LocalPath, time.Now(), time.Unix(fnode.Pulltime, 0))
				}
				if buf, err := json.Marshal(fnode); err != nil {
					return err
				} else if err := bkt.Put([]byte(fnode.LocalPath), buf); err != nil {
					return err
				}

			case deleteNode:
				if fnode.Dir {
					delayListDel = append(delayListDel, fnode)
				} else {
					os.RemoveAll(fnode.LocalPath)
					bkt.Delete([]byte(fnode.LocalPath))
					fmt.Println(fmt.Sprintf("delete file: %s", fnode.LocalPath))
				}

			case conflictNode:
				conflictList = append(conflictList, fnode.LocalPath)
				fmt.Println(fmt.Sprintf("file: %s is conflict", fnode.LocalPath))
			}
		}

		if len(conflictList) != 0 {
			for _, dnode := range delayListDel {
				candelete := true
				for _, conflictnode := range conflictList {
					if len(conflictnode) >= len(dnode.LocalPath) {
						if newfinfodir := []byte(conflictnode)[0:len(dnode.LocalPath)]; dnode.LocalPath == string(newfinfodir) {
							candelete = false
							break
						}
					}
				}
				if candelete {
					os.RemoveAll(dnode.LocalPath)
					bkt.Delete([]byte(dnode.LocalPath))
					fmt.Println(fmt.Sprintf("delete dir: %s", dnode.LocalPath))
				}
			}
		} else {
			for _, dnode := range delayListDel {
				os.RemoveAll(dnode.LocalPath)
				bkt.Delete([]byte(dnode.LocalPath))
				fmt.Println(fmt.Sprintf("delete dir: %s", dnode.LocalPath))
			}
		}

		return nil
	})

	fmt.Println(fmt.Sprintf("%v -> %s pull ok", remote, local))
	return
}

func fail(err interface{}) {
	if err != nil {
		fmt.Println(err)
	}
	os.Exit(-1)
}

func writeFile(path string, bytes []byte, mode os.FileMode) error {
	parent := filepath.Dir(path)
	if _, e := os.Stat(parent); os.IsNotExist(e) {
		if e := os.MkdirAll(parent, os.ModePerm); e != nil {
			return e
		}
	}

	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(bytes)
	return err
}

func getStream(pathOrString string) (io.ReadCloser, error) {

	fi, err := os.Stat(pathOrString)
	if err != nil {
		return nil, err
	}

	if fi.IsDir() {
		return nil, &os.PathError{
			Op:   "Open",
			Path: pathOrString,
			Err:  errors.New("Path: '" + pathOrString + "' is a directory"),
		}
	}

	f, err := os.Open(pathOrString)
	if err == nil {
		return f, nil
	}

	return nil, &os.PathError{
		Op:   "Open",
		Path: pathOrString,
		Err:  err,
	}
}
